package com.example.demo.advice;

import com.alibaba.fastjson.JSON;
import com.example.demo.common.enums.EnumResultCode;
import com.example.demo.common.exception.BusinessErrorException;
import com.example.demo.common.exception.BusinessException;
import com.example.demo.dto.ResponseResult;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * 全局统一异常处理类
 *
 * @ControllerAdvice 与 @RestControllerAdvice的来源、关系、作用
 * 在Spring 3.2中，新增了@ControllerAdvice、@RestControllerAdvice 注解，都是对Controller进行增强，
 * @RestControllerAdvice = @ControllerAdvice + @ResponseBody
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandlerAdvice implements ResponseBodyAdvice {


    /**
     * 自定义业务异常, 不打印ERROR级别日志
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseResult handleException(BusinessException e, HttpServletRequest request) {
        log.info("[业务异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), e.getMessage(), e);
        return ResponseResult.buildError(e.getErrorCode(), e.getMessage());
    }

    /**
     * 自定义业务异常, 打印ERROR级别日志
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(BusinessErrorException.class)
    public ResponseResult handleException(BusinessErrorException e, HttpServletRequest request) {
        log.error("[业务error异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), e.getMessage(), e);
        return ResponseResult.buildError(e.getMessage());
    }

    /**
     * 专门用来捕获和处理Controller层的空指针异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseResult handleException(NullPointerException e, HttpServletRequest request) {
        log.error("[空指针异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), e.getMessage(), e);
        return ResponseResult.buildError("服务暂时不可用,请您稍后再试!");
    }


    /**
     * 参数合法性校验异常
     *
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseResult validationBodyException(MethodArgumentNotValidException e, HttpServletRequest request) {
        BindingResult result = e.getBindingResult();
        // 错误描述
        String errorMsg = null;
        // 消息列表
        List<String> messages = new ArrayList<>();
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            for (ObjectError error : errors) {
                if (error instanceof FieldError) {
                    FieldError fieldError = (FieldError) error;
                    messages.add(fieldError.getDefaultMessage());
                }
            }
            errorMsg = String.join(",", messages);
        }
        String message = e.getMessage();
        log.warn("[参数校验异常], error_msg={}\n\n\n");
        e.printStackTrace();
        log.warn("[参数校验异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), errorMsg);
        return ResponseResult.buildError(errorMsg);
    }

    /**
     * Http消息不可读异常。
     * <p>
     * 报错原因包括（不完全的列举）：
     * <p>
     * （1）缺少请求体（RequestBody）异常;
     * <p>
     * （2）无效格式异常。比如：参数为数字，但是前端传递的是字符串且无法解析成数字。
     * <p>
     * （3）Json解析异常（非法Json格式）。传递的数据不是合法的Json格式。比如：key-value对中的value(值)为String类型，却没有用双引号括起来。
     * <p>
     * 举例：
     * （1）缺少请求体（RequestBody）异常。报错：
     * DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException:
     * Required request body is missing:
     * public void com.example.web.user.controller.UserController.addUser(com.example.web.model.param.UserAddParam)]
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
        // 错误描述
        String errorMsg = null;
        Throwable rootCause = e.getRootCause();
        if (rootCause instanceof InvalidFormatException) {
            String format = "HttpMessageNotReadableException-InvalidFormatException（Http消息不可读异常-无效格式异常）：%s";
            errorMsg = String.format(format, e.getMessage());
            log.error("[数据类型不匹配异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), errorMsg, e);
        } else {
            errorMsg = String.format("HttpMessageNotReadableException（Http消息不可读异常）：%s", e.getMessage());
            log.error("[Http消息不可读异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), errorMsg, e);
        }
        return ResponseResult.buildError(errorMsg);
    }


    @ExceptionHandler(Exception.class)
    public ResponseResult handleException(Exception e, HttpServletRequest request) {
        log.warn("[未知异常], uri={}, param={}, error_msg={}", request.getRequestURI(), JSON.toJSONString(request.getQueryString()), e.getMessage(), e);
        return ResponseResult.buildError(EnumResultCode.ERROR);
    }

    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {

    }

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        return body;
    }

}
